﻿using DJLang;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace BlockOS.Client.Res
{
    public class BlockModelDef : ICloneable
    {
        public FacingMask opacity;
        public DJList bindPoints;
        public IDJValue faces;

        private List<TextureBindPoint> m_bindPoints = null;
        private List<TextureBindPoint> BindPoints
        {
            get
            {
                if (m_bindPoints == null) { m_bindPoints = GetBindPoints(bindPoints); }
                return m_bindPoints;
            }
        }

        public BlockModelDef(FacingMask opacity, DJList bindPoints, IDJValue faces)
        {
            this.opacity = opacity;
            this.bindPoints = bindPoints;
            this.faces = faces;
        }

        public BlockModelDef BindTextures(DJEnv env, DJList list)
        {
            while (list.Count > 0)
            {
                var bindSeq = list.PopAs<DJList>();
                var name = bindSeq.PopAs<DJSymbol>();
                var textureName = bindSeq.PopManaged<string>();

                Vector4 uv = new(0, 0, 1, 1);
                if (bindSeq.Count > 0) uv = bindSeq.Pop().Evaluate(env).GetValue<Vector4>();

                if (BindPoints.Find(x => x.name == name.name) is TextureBindPoint point)
                {
                    point.Bind(textureName, uv);
                }
            }
            return this;
        }

        public object Clone()
        {
            return new BlockModelDef(opacity, bindPoints.Clone() as DJList, faces.Clone());
        }

        public BlockModel GetBlockModel(DJEnv env, Dictionary<string, Func<Vector2, Vector2>> converters)
        {
            var callEnv = new DJEnv();
            callEnv.parent = env;
            foreach (var bindPoint in BindPoints)
            {
                if (bindPoint.Target is TextureBindPoint.TextureTarget target)
                {
                    var converter = converters[target.textureName];
                    callEnv.Put(bindPoint.name, new DJRef<Func<Vector2, Vector2>>(x => converter(UVConverts.GetLocalUV(target.uv, x))));
                }
                else
                {
                    callEnv.Put(bindPoint.name, new DJRef<Func<Vector2, Vector2>>(converters["Default:Block/unknown"]));
                }
            }

            var facesList = callEnv.ForceEval(faces.Clone()) as DJList;
            BlockModel.Face[] modelfaces = facesList.Select(x => x.GetManaged<BlockModel.Face>()).ToArray();

            return new BlockModel(opacity, modelfaces);
        }

        public static List<TextureBindPoint> GetBindPoints(DJList bindPoints)
        {
            var ans = new List<TextureBindPoint>();
            Queue<(string, string)> rebindTasks = new();
            while (bindPoints.Count > 0)
            {
                var bindPoint = bindPoints.Pop();
                if (bindPoint is DJSymbol symbol)
                {
                    ans.Add(new TextureBindPoint(symbol.name));
                }
                else if (bindPoint is DJList rebind)
                {
                    var dst = rebind.PopAs<DJSymbol>();
                    var src = rebind.PopAs<DJSymbol>();

                    rebindTasks.Enqueue((dst.name, src.name));
                }
            }

            while (rebindTasks.Count > 0)
            {
                var (dst, src) = rebindTasks.Dequeue();
                var dstBindPoint = ans.Find(x => x.name == dst);
                var srcBindPoint = ans.Find(y => y.name == src);
                dstBindPoint.SetAlias(srcBindPoint);
            }
            return ans;
        }
    }
}
